package dataflow.core;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.locks.Lock;

import org.apache.log4j.Logger;

import dataflowScheme.Port;
import dataflowScheme.PortSet;
import dataflowScheme.SynchroPort;

public class StorageExecutionEvent implements ExecutionEvent, Runnable {

	private Logger log = Logger.getLogger(TaskExecutionEvent.class);
	
	private final Storage storage;
	private final int portset;
	private final StorageHandler handler = StorageHandler.getInstance();
	private final Method runMethod;
	private final Class<?> sourceClass;
	private final Map<String, Object> values;
	private final Map<String, Method> portMethods;
	
	public StorageExecutionEvent(Storage storage, int portset) {
		this.storage = storage;
		this.portset = portset;
		this.runMethod = storage.getRunMethod();
		this.sourceClass = storage.getSourceClass();
		this.values = storage.getPortValues();
		this.portMethods = storage.getPortMethods();
	}
	
	@Override
	public void execute(ExecutionEventParser executionEventParser,
			ExecutorService threadExecutor) {
		threadExecutor.submit(this);
	}
	
	@Override
	public void run() {
		int returnValue = -1;
		Lock lock = handler.getSourceLock(sourceClass);
		try{
			returnValue = invoke(lock);
		} catch(Exception e){
			log.error("Unknown exception while processing '" + storage.getPE().getName() + "'.", e);
		} finally {
			lock.unlock();
			storage.receiveResult(returnValue);
		}
	}
	
	private int invoke(Lock lock){
		
		dataflowScheme.PE ePe = storage.getPE();

		log.debug("trying to acquire lock of '" + ePe.getName());
		lock.lock();
		log.debug("starting executing '" + ePe.getName() + "'");
		
		PortSet ps = PE.findPortSetByID(ePe, portset);
		if(ps == null){
			log.error("Bad number of portset.");
			return -1;
		}
		
		Object sourceInstance = getSourceInstance();
		if(sourceInstance == null){
			return -1;
		}
	
		if(!initializeSourcePorts(ps, sourceInstance, ePe)){
			return -1;
		}
		
		try{
			return (Integer)runMethod.invoke(sourceInstance, portset);
		} catch (InvocationTargetException ite){
			log.error("Invocation of main method failed.", ite);
		} catch (IllegalAccessException iae){
			log.error("Method '" + runMethod.getName() + "' have to be public.",iae);
		} catch (Exception e){
			log.error("Unknown error while executing core of '" + ePe.getName() + "'.");
		}
			
		return -1;
	}
	
	private boolean initializeSourcePorts(PortSet ps, Object sourceInstance, dataflowScheme.PE ePe){
		for (Port p : ps.getPort()){
			
			if (p instanceof SynchroPort){
				continue;
			}
			
			Object value = values.remove(p.getName());
			if(value == null){
				log.error("Sth wrong, can not invoke portset method because necessary value doens't exist.");
				return false;
			}
			
			Method m = portMethods.get(p.getName());
			try{
				m.invoke(sourceInstance, value);
			}catch(InvocationTargetException ite){
				log.error("Invocation of port '" + p.getName() + "' failed.", ite);
				return false;
			} catch (IllegalArgumentException e) {
				log.error("Method '" + m.getName() + "' of '" + ePe.getName() + "' has bad arguments.", e);
				return false;
			} catch (IllegalAccessException e) {
				log.error("Method '" + m.getName() + "' of '" + ePe.getName() + "' have to be public.",e);
				return false;
			}
		}
		return true;
	}
	
	private Object getSourceInstance(){
		Object sourceInstance = storage.getSourceInstance();
		if(sourceInstance == null){
			try{
				sourceInstance = handler.getSourceInstance(sourceClass);
				storage.setSourceInstance(sourceInstance);
				
			} catch(IllegalAccessException ilae){
				log.error("Cannot create instance of Storage", ilae);
			} catch(InstantiationException ie){
				log.error("Cannot create instance of Storage", ie);
			}
		}
		
		return sourceInstance;
	}

}
